package club.kingyin.easycache.key;

import club.kingyin.easycache.component.InjectPostProcess;
import club.kingyin.easycache.component.Pram;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.annotation.Annotation;
import java.util.*;

@Slf4j
public abstract class AbstractEasyCacheKey implements EasyCacheKey {

    protected final String CURRENT_PARAM_KEY = "tmp_key";

    protected final String DEFAULT_DELIMIT_GROUP = ":";
    protected final String DEFAULT_DELIMIT_PRAM = "&";

    protected JSONObject prams = new JSONObject();

    protected String methodName;

    protected String module;

    private String key;

    public Set<String> excludes = new HashSet<>();

    public Map<String,Object> ancillary = new HashMap<>();

    private boolean start;

    private InjectPostProcess[] postProcesses;

    protected abstract void setPrams();

    protected AbstractEasyCacheKey() {
    }

    /**
     * 提供附带条件
     * @param key 附带条件key
     * @param anc 附属品
     */
    public void affiliated(String key,Object anc) {
        this.ancillary.put(key, anc);
    }

    /**
     * 获取附属品，但不会删除
     * @param key 附带条件
     * @return 附属品
     */
    public Object obtain(String key) {
        return this.ancillary.get(key);
    }

    /**
     * 获取附属品，获取即删除
     * @param key 附带条件
     * @return 附属品
     */
    public Object consume(String key) {
        return this.ancillary.remove(key);
    }

    private void init() {
        if (!start) {
            setPrams();
            start = true;
        }
    }

    public void addIgnoreParma(String param) {
        if (prams.containsKey(param)) {
            excludes.add(param);
        }
    }

    public void setKey(String key) {
        init();
        this.key = key;
    }

    public void setPostProcesses(InjectPostProcess[] postProcesses) {
        this.postProcesses = postProcesses;
    }

    public InjectPostProcess[] getPostProcesses() {
        return postProcesses;
    }

    @Override
    public void setModule(String module) {
        this.module = module;
    }

    @Override
    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public static boolean paramIsBasicReferenceType(Object pram) {
        if (pram instanceof String) {
            return true;
        }
        if (pram instanceof Integer) {
            return true;
        }
        if (pram instanceof Long) {
            return true;
        }
        if (pram instanceof Double) {
            return true;
        }
        if (pram instanceof Short) {
            return true;
        }
        if (pram instanceof Byte) {
            return true;
        }
        if (pram instanceof Character) {
            return true;
        }
        return pram instanceof Boolean;
    }

    public void injectionParameter(String paramName, Object param, Annotation[] annotations) throws IllegalAccessException {
        Pram pram = new Pram(paramName, param);
        for (InjectPostProcess postProcess : postProcesses) {
            postProcess.postProcessBeforeInject(annotations,this,pram);
        }
        boolean isInject = true;
        for (InjectPostProcess postProcess : postProcesses) {
            isInject = postProcess.postProcessPreInject(isInject,annotations,this,pram);
        }
        // before
        if (isInject) {
            log.debug("模块：[{}] 方法：[{}] 参数注入 {} -> {}", module, methodName, pram.getPramName(), pram.getPram());
            this.prams.put(pram.getPramName(),pram.getPram());
        }
        // after
        for (InjectPostProcess postProcess : postProcesses) {
            postProcess.postProcessAfterInject(isInject,annotations, this,pram.getPramName(),pram.getPram());
        }
    }

    @Override
    public JSONObject getPrams() {
        return prams;
    }

    @Override
    public boolean containsPramName(String pramName) {
        init();
        return prams.containsKey(pramName);
    }

    @Override
    public boolean isEmptyByPramName(String pramName) {
        init();
        return !containsPramName(pramName) || prams.get(pramName) == null;
    }

    @Override
    public String getMethodName() {
        init();
        return methodName;
    }

    @Override
    public String getModule() {
        return module;
    }

    public String getKey() {
        init();
        return Optional.ofNullable(key).filter(StringUtils::isNotBlank).orElseGet(() -> {
            if (prams.isEmpty()) {
                return coalescence();
            } else {
                // prams 和 excludes 交集
                return getKeyWithPram(prams.keySet().stream().filter(p -> !excludes.contains(p)).toArray(String[]::new));
            }
        });
    }

    private Object getValue(String pram) {
        init();
        if (prams.containsKey(pram)) {
            return prams.get(pram);
        }
        String[] fields = pram.split("\\.");
        JSONObject tmp = prams.getJSONObject(fields[0]);
        for (int i = 1; i < fields.length-1; i++) {
            tmp = tmp.getJSONObject(fields[i]);
        }
        return tmp.get(fields[fields.length-1]);
    }

    private <T>T getValue(String pram,Class<T> type) {
        init();
        String[] fields = pram.split("\\.");
        JSONObject tmp = prams.getJSONObject(fields[0]);
        for (int i = 1; i < fields.length-1; i++) {
            tmp = tmp.getJSONObject(fields[i]);
        }
        return tmp.getObject(fields[fields.length-1],type);
    }

    protected Map<String,Object> createPramString(String... pramName) {
        Map<String,Object> ps = new HashMap<>();
        String key;
        for (String pram : pramName) {
            key = "@";
            // 自定义参数名
            if (pram.contains("=")) {
                String[] split = pram.split("=");
                pram = split[1];
                key = split[0];
            }
            // 属于对象中的参数
            if (pram.contains(".")) {
                String[] split = pram.split("\\.");
                key = "@".equals(key)?split[split.length-1]:key;
                ps.put(key,getValue(pram));
            }
            else if(!isEmptyByPramName(pram)) {
                key = "@".equals(key)?pram:key;
                ps.put(key,prams.get(pram));
            }
        }
        return ps;
    }

    @Override
    public String getKeyWithPram(String... pramName) {
        init();
        Map<String, Object> ps = createPramString(pramName);
        affiliated(CURRENT_PARAM_KEY,ps);
        this.key = coalescence();
        log.debug("模块：[{}] 方法：[{}] 构建存储key -> {}",module, methodName, key);
        return key;
    }

    @Override
    public <T> T getPram(String name, Class<T> type) {
        init();
        if (name.contains(".")) {
            return getValue(name,type);
        }
        return prams.getObject(name,type);
    }

    public Object getPram(String name) {
        init();
        if (name.startsWith("*.")) {
            String s = prams.keySet().stream().filter(p -> p.endsWith(name.replace("*.", ""))).findFirst().orElse(null);
            return s== null ? null : prams.get(s);
        }
        if (name.contains(".")) {
            return getValue(name);
        }
        return prams.get(name);
    }

    @Override
    public boolean equalsModuleANdMethodName(EasyCacheKey key) {
        init();
        // 比较出现错误 module=:test methodName=ky-test
        return getModule().equals(key.getModule()) && getMethodName().equals(key.getMethodName());
    }

    @Override
    public boolean equalsPram(EasyCacheKey key, String... pramName) {
        init();
        return getKeyWithPram(pramName).equals(key.getKeyWithPram(pramName));
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AbstractEasyCacheKey that = (AbstractEasyCacheKey) o;
        return start == that.start && Objects.equals(prams, that.prams) && Objects.equals(methodName, that.methodName) && Objects.equals(module, that.module) && Objects.equals(key, that.key);
    }

    @Override
    public int hashCode() {
        return Objects.hash(prams, methodName, module, key, start);
    }

    @Override
    public boolean minContainsPramByKeys(EasyCacheKey... key) {
        init();
        for (EasyCacheKey easyCacheKey : key) {
            // 属于当前模块并且方法相同
            if (equalsModuleANdMethodName(easyCacheKey)) {
                // 最小参数比较
                JSONObject prams = ((JSONObject) easyCacheKey.getPrams());
                // this 的参数 包含了 easyCacheKey的所有参数则判断为需要删除项
                for (String pramName : this.prams.keySet()) {
                    Object value = easyCacheKey.getPram("*." + pramName);
                    if (!this.prams.get(pramName).equals(value)) {
                        return false;
                    }
                }
                return true;
            }
        }
        return false;
    }

    @Override
    public String coalescence() {
        return prefix() + JSON.toJSONString(Optional.ofNullable(obtain(CURRENT_PARAM_KEY)).orElse(Collections.emptyMap()));
    }

    @Override
    public String prefix() {
        return module+DEFAULT_DELIMIT_GROUP+methodName+DEFAULT_DELIMIT_PRAM;
    }

    @Override
    public EasyCacheKey parse(String sources) {
        log.debug("模块：[{}] 方法：[{}] 原始Key -> {}",module,methodName,sources);
        String module = sources.split(DEFAULT_DELIMIT_GROUP)[0];
        String methodName = sources.substring(sources.indexOf(DEFAULT_DELIMIT_GROUP)+1, sources.indexOf(DEFAULT_DELIMIT_PRAM));
        JSONObject ps = JSONObject.parseObject(sources.split(DEFAULT_DELIMIT_PRAM)[1]);
        EasyCacheKey key = new DefaultEasyCacheKey
                .Builder().setModule(module).setMethodName(methodName).setPram(ps).build();
        log.debug("模块：[{}] 方法：[{}] 解析Key -> {}",module,methodName,key);
        return key;
    }

    @Override
    public String toString() {
        return "全局：["+module+"]-"+"["+methodName+"()]-"+getPrams()+"-"+excludes;
    }
}
